package pr.lanmu.config.web;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import org.hibernate.validator.internal.engine.ConstraintViolationImpl;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import pr.lanmu.config.constant.Code;
import pr.lanmu.config.util.Current;
import pr.lanmu.config.util.Log;
import pr.lanmu.config.util.Res;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 全局异常捕获和分类，根据不同类别异常，输出不同日志，并定义返回
 */
@RestControllerAdvice
public class ExceptionController {
    private static final Map<Class<?>, Code> SYSTEM_WARNING_CACHE_MAP = new HashMap<>(16);
    private static final Map<Class<?>, Code> SYSTEM_ERROR_CACHE_MAP = new HashMap<>(16);

    static {
        SYSTEM_WARNING_CACHE_MAP.put(NoHandlerFoundException.class, Code.NOT_FOUND_EXCEPTION);
        SYSTEM_ERROR_CACHE_MAP.put(NoResourceFoundException.class, Code.NOT_FOUND_EXCEPTION);
        SYSTEM_ERROR_CACHE_MAP.put(MaxUploadSizeExceededException.class, Code.MAX_UPLOAD_SIZE_EXCEPTION);
        SYSTEM_ERROR_CACHE_MAP.put(MissingServletRequestParameterException.class, Code.VALID_PARAM_ERROR_EXCEPTION);
    }

    @ExceptionHandler(Exception.class)
    public Res<?> handleException(Exception exception) {
        String message = exception.getMessage();
        String exceptionName = exception.getClass()
                .getName();
        Code code;
        if (SYSTEM_WARNING_CACHE_MAP.containsKey(exception.getClass())) {
            //筛选框架异常
            code = SYSTEM_WARNING_CACHE_MAP.get(exception.getClass());
            doLogWarn(exception, exceptionName, message);
        } else if (SYSTEM_ERROR_CACHE_MAP.containsKey(exception.getClass())) {
            code = SYSTEM_ERROR_CACHE_MAP.get(exception.getClass());
            doLogError(exception, exceptionName, message);
        } else if (exception instanceof CustomException) {
            //筛选手动抛出的自定义内容的业务异常，不做日志记录
            Log.warn("自定义业务异常被抛出----异常信息: {}", message);
            code = new Code(501, message);
            doLogWarn(exception, exceptionName, message);
        } else if (exception instanceof ValidateException) {
            //筛选手动抛出的自定义内容的业务异常，不做日志记录
            code = new Code(400, message);
        } else {
            //筛选手动抛出的规范异常
            code = Code.check(message);
            if (Objects.nonNull(code)) {
                Log.warn("自定义规范异常被抛出----异常信息: {}", message);
                doLogWarn(exception, exceptionName, message);
            } else {
                doLogError(exception, exceptionName, message);
                code = Code.RUN_ERROR_EXCEPTION;
            }
        }
        return Res.builder()
                .code(code)
                .build();
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Res<?> jsonMethodArgumentNotValidException(HttpServletRequest ignore, MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<String> errors = bindingResult.getAllErrors()
                .parallelStream()
                .map(err -> {
                    if (err instanceof FieldError) {
                        try {
                            Field field = ObjectError.class.getDeclaredField("source");
                            field.setAccessible(true);
                            Object o = field.get(err);
                            Field rootBeanClass = ConstraintViolationImpl.class.getDeclaredField("rootBeanClass");
                            rootBeanClass.setAccessible(true);
                            Class<?> clazz = (Class<?>) rootBeanClass.get(o);
                            Field declaredField = clazz.getDeclaredField(((FieldError) err).getField());
                            declaredField.setAccessible(true);
                            Schema annotation = declaredField.getAnnotation(Schema.class);
                            String value = annotation.description();
                            return value + ":" + err.getDefaultMessage();
                        } catch (Exception ex) {
                            Log.error("获取字段注解失败", ex);
                        }
                        return ((FieldError) err).getField() + ":" + err.getDefaultMessage();
                    } else {
                        return err.getObjectName() + ":" + err.getDefaultMessage();
                    }
                })
                .collect(Collectors.toList());
        HttpServletRequest request = Current.getInfo()
                .getRequest();
        Log.warn("参数校验未通过----请求路径: {},请求方式: {},错误信息: {}", request
                .getRequestURI(), request
                .getMethod(), errors);
        return Res.builder()
                .code(new Code(400, errors.getFirst()))
                .build();
    }

    private void doLogWarn(Exception exception, String exceptionName, String message) {
        Log.warn("异常拦截----异常类:{},异常信息:{}", exceptionName, message);
        Log.warn("异常堆栈信息", exception);
    }

    private void doLogError(Exception exception, String exceptionName, String message) {
        Log.error("异常拦截----异常类:{},异常信息:{}", exceptionName, message);
        Log.error("异常堆栈信息", exception);
    }
}